package com.ideaaedi.springcloud.jd.user.config.ext;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONValidator;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ideaaedi.commonspring.annotation.RecordParameters;
import com.ideaaedi.springcloud.jd.commonds.entity.BaseCodeMsgEnum;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysRolePO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysUserPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysUserRoleRePO;
import com.ideaaedi.springcloud.jd.commonds.enums.user.LoginSceneEnum;
import com.ideaaedi.springcloud.jd.commonds.enums.user.UserTypeEnum;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysUserLoginSuccessEvent;
import com.ideaaedi.springcloud.jd.commonds.ext.UsernameExt;
import com.ideaaedi.springcloud.jd.commonspring.mybatisplus.tenant.TenantProvider;
import com.ideaaedi.springcloud.jd.user.exception.JdOAuth2Exception;
import com.ideaaedi.springcloud.jd.user.service.SysRoleService;
import com.ideaaedi.springcloud.jd.user.service.SysUserRoleReService;
import com.ideaaedi.springcloud.jd.user.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 用户详情服务
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 1.2.0
 */
@Slf4j
@Component
@RecordParameters
public class JdUserDetailsService implements UserDetailsService {
    
    private PasswordEncoder passwordEncoder;
    
    @Resource
    private ApplicationContext applicationContext;
    
    @Resource
    private SysUserService sysUserService;
    
    @Resource
    private SysRoleService sysRoleService;
    
    @Resource
    private SysUserRoleReService sysUserRoleReService;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (passwordEncoder == null) {
            passwordEncoder = applicationContext.getBean(PasswordEncoder.class);
            Objects.requireNonNull(passwordEncoder, "passwordEncoder cannot be null");
        }
        // 是否是对username的增强
        UsernameExt usernameExt = null;
        if (username != null) {
            JSONValidator.Type type = JSONValidator.from(username).getType();
            if (JSONValidator.Type.Object == type) {
                UsernameExt tmp = JSON.parseObject(username, UsernameExt.class);
                tmp.valid();
                final boolean hasUsername = StringUtils.isNotBlank(tmp.getUsername());
                final boolean hasType = tmp.getType() != null;
                if ((hasUsername && hasType)) {
                    usernameExt = tmp;
                }
            }
        }
        JdUserDetails userDetails;
        if (usernameExt == null) {
            userDetails = TenantProvider.execGlobal(() -> accountNoLogin(username));
        } else {
            UsernameExt.UsernameType type = usernameExt.getType();
            String realUsername = usernameExt.getUsername();
            switch (type) {
                case ACCOUNT_NO_PASSWORD: {
                    userDetails = TenantProvider.execGlobal(() -> accountNoLogin(realUsername));
                }
                break;
                case APPLET_DIRECT_PHONE: {
                    // 微信小程序手机号直接登录时，extData就是租户名
                    String phone = usernameExt.getUsername();
                    String tenant = usernameExt.getTenant();
                    UserTypeEnum userType = usernameExt.getUserType();
                    LoginSceneEnum loginSceneEnum = usernameExt.getLoginSceneEnum();
                    JdUserDetails jdUserDetails = TenantProvider.exec(() -> phoneLogin(userType, phone), tenant);
                    jdUserDetails.setLoginScene(loginSceneEnum);
                    userDetails = jdUserDetails;
                }
                break;
                default:
                    throw new UnsupportedOperationException("un-support type -> " + type);
            }
        }
        
        applicationContext.publishEvent(new SysUserLoginSuccessEvent(userDetails));
        return userDetails;
    }
    
    
    /**
     * 账密登录
     */
    @NonNull
    private JdUserDetails accountNoLogin(String accountNo) {
        SysUserPO sysUser = sysUserService.getOne(new LambdaQueryWrapper<SysUserPO>()
                .eq(SysUserPO::getAccountNo, accountNo)
        );
        if (sysUser == null) {
            throw new JdOAuth2Exception(BaseCodeMsgEnum.USER_NON_EXIST_ERROR);
        }
        JdUserDetails jdUserDetails = getJdUserDetails(sysUser);
        jdUserDetails.setLoginScene(LoginSceneEnum.USERNAME);
        return jdUserDetails;
    }
    
    /**
     * 手机号登录
     *
     * @param userType 用户类型.
     * <p>
     * 相同租户下，不同的用户类型，可以拥有相同的手机号、证件类型证件号. 因此，如果不是通过accountNo账密登录的话，还需要一个用户类型进行匹配，否则可能匹配出多条继续
     */
    @NonNull
    private JdUserDetails phoneLogin(@Nullable UserTypeEnum userType, @NonNull String phone) {
        List<SysUserPO> sysUserList = sysUserService.list(new LambdaQueryWrapper<SysUserPO>()
                .eq(userType != null, SysUserPO::getType, userType)
                .eq(SysUserPO::getPhone, phone)
        );
        if (CollectionUtils.isEmpty(sysUserList)) {
            throw new JdOAuth2Exception(BaseCodeMsgEnum.USER_NON_EXIST_ERROR);
        }
        if (sysUserList.size() > 1) {
            List<UserTypeEnum> matchedUserTypeList =
                    sysUserList.stream().map(SysUserPO::getType).collect(Collectors.toList());
            throw new JdOAuth2Exception(JSON.toJSONString(matchedUserTypeList),
                    BaseCodeMsgEnum.NEED_POINT_USER_TYPE_TO_LOGIN);
        }
        JdUserDetails jdUserDetails = getJdUserDetails(sysUserList.get(0));
        jdUserDetails.setPassword(passwordEncoder.encode(UsernameExt.APPLET_DIRECT_PHONE_PASSWORD));
        return jdUserDetails;
    }
    
    @NotNull
    private JdUserDetails getJdUserDetails(SysUserPO sysUser) {
        Set<Long> roleIds = sysUserRoleReService.list(new LambdaQueryWrapper<SysUserRoleRePO>()
                .select(SysUserRoleRePO::getRoleId)
                .eq(SysUserRoleRePO::getUserId, sysUser.getId())
        ).stream().map(SysUserRoleRePO::getRoleId).collect(Collectors.toSet());
        
        JdUserDetails jdUserDetails = new JdUserDetails();
        BeanUtils.copyProperties(sysUser, jdUserDetails);
        // 角色
        if (!CollectionUtils.isEmpty(roleIds)) {
            List<SysRolePO> roles = sysRoleService.listByIds(roleIds);
            jdUserDetails.setRoles(roles);
        }
        return jdUserDetails;
    }
}
